diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-13 04:41:33 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-13 05:08:29 +0200 |
| commit | 7d4e166678c35d48b22b8d1d05639f981a956a8b (patch) | |
| tree | eb596d4f4609053524224169c247db24052db353 /apps/web/src/app/editor/[project_id] | |
| parent | cad88ee4d1c9e06e7529b88ef5e5d3fdf3f89149 (diff) | |
| download | opencut-7d4e166678c35d48b22b8d1d05639f981a956a8b.tar.gz | |
fix: text measurement, rotation handle, video cache races, playback sync
Made-with: Cursor
Diffstat (limited to 'apps/web/src/app/editor/[project_id]')
| -rw-r--r-- | apps/web/src/app/editor/[project_id]/page.tsx | 255 |
1 files changed, 142 insertions, 113 deletions
diff --git a/apps/web/src/app/editor/[project_id]/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx index 38a25d4c..8456d624 100644 --- a/apps/web/src/app/editor/[project_id]/page.tsx +++ b/apps/web/src/app/editor/[project_id]/page.tsx @@ -1,113 +1,142 @@ -"use client";
-
-import { useParams } from "next/navigation";
-import {
- ResizablePanelGroup,
- ResizablePanel,
- ResizableHandle,
-} from "@/components/ui/resizable";
-import { AssetsPanel } from "@/components/editor/panels/assets";
-import { PropertiesPanel } from "@/components/editor/panels/properties";
-import { Timeline } from "@/components/editor/panels/timeline";
-import { PreviewPanel } from "@/components/editor/panels/preview";
-import { EditorHeader } from "@/components/editor/editor-header";
-import { EditorProvider } from "@/components/providers/editor-provider";
-import { Onboarding } from "@/components/editor/onboarding";
-import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog";
-import { usePanelStore } from "@/stores/panel-store";
-import { usePasteMedia } from "@/hooks/use-paste-media";
-import { MobileGate } from "@/components/editor/mobile-gate";
-
-export default function Editor() {
- const params = useParams();
- const projectId = params.project_id as string;
-
- return (
- <MobileGate>
- <EditorProvider projectId={projectId}>
- <div className="bg-background flex h-screen w-screen flex-col overflow-hidden">
- <EditorHeader />
- <div className="min-h-0 min-w-0 flex-1">
- <EditorLayout />
- </div>
- <Onboarding />
- <MigrationDialog />
- </div>
- </EditorProvider>
- </MobileGate>
- );
-}
-
-function EditorLayout() {
- usePasteMedia();
- const { panels, setPanel } = usePanelStore();
-
- return (
- <ResizablePanelGroup
- direction="vertical"
- className="size-full gap-[0.18rem]"
- onLayout={(sizes) => {
- setPanel("mainContent", sizes[0] ?? panels.mainContent);
- setPanel("timeline", sizes[1] ?? panels.timeline);
- }}
- >
- <ResizablePanel
- defaultSize={panels.mainContent}
- minSize={30}
- maxSize={85}
- className="min-h-0"
- >
- <ResizablePanelGroup
- direction="horizontal"
- className="size-full gap-[0.19rem] px-3"
- onLayout={(sizes) => {
- setPanel("tools", sizes[0] ?? panels.tools);
- setPanel("preview", sizes[1] ?? panels.preview);
- setPanel("properties", sizes[2] ?? panels.properties);
- }}
- >
- <ResizablePanel
- defaultSize={panels.tools}
- minSize={15}
- maxSize={40}
- className="min-w-0"
- >
- <AssetsPanel />
- </ResizablePanel>
-
- <ResizableHandle withHandle />
-
- <ResizablePanel
- defaultSize={panels.preview}
- minSize={30}
- className="min-h-0 min-w-0 flex-1"
- >
- <PreviewPanel />
- </ResizablePanel>
-
- <ResizableHandle withHandle />
-
- <ResizablePanel
- defaultSize={panels.properties}
- minSize={15}
- maxSize={40}
- className="min-w-0"
- >
- <PropertiesPanel />
- </ResizablePanel>
- </ResizablePanelGroup>
- </ResizablePanel>
-
- <ResizableHandle withHandle />
-
- <ResizablePanel
- defaultSize={panels.timeline}
- minSize={15}
- maxSize={70}
- className="min-h-0 px-3 pb-3"
- >
- <Timeline />
- </ResizablePanel>
- </ResizablePanelGroup>
- );
-}
+"use client"; + +import { useParams } from "next/navigation"; +import { + ResizablePanelGroup, + ResizablePanel, + ResizableHandle, +} from "@/components/ui/resizable"; +import { AssetsPanel } from "@/components/editor/panels/assets"; +import { PropertiesPanel } from "@/components/editor/panels/properties"; +import { Timeline } from "@/components/editor/panels/timeline"; +import { PreviewPanel } from "@/components/editor/panels/preview"; +import { EditorHeader } from "@/components/editor/editor-header"; +import { EditorProvider } from "@/components/providers/editor-provider"; +import { Onboarding } from "@/components/editor/onboarding"; +import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog"; +import { usePanelStore } from "@/stores/panel-store"; +import { usePasteMedia } from "@/hooks/use-paste-media"; +import { MobileGate } from "@/components/editor/mobile-gate"; +import { useState } from "react"; +import { useEditor } from "@/hooks/use-editor"; +import { Cancel01Icon } from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { Button } from "@/components/ui/button"; + +export default function Editor() { + const params = useParams(); + const projectId = params.project_id as string; + + return ( + <MobileGate> + <EditorProvider projectId={projectId}> + <div className="bg-background flex h-screen w-screen flex-col overflow-hidden"> + <DegradedRendererBanner /> + <EditorHeader /> + <div className="min-h-0 min-w-0 flex-1"> + <EditorLayout /> + </div> + <Onboarding /> + <MigrationDialog /> + </div> + </EditorProvider> + </MobileGate> + ); +} + +function DegradedRendererBanner() { + const isDegraded = useEditor((e) => e.renderer.isDegraded); + const [dismissed, setDismissed] = useState(false); + if (!isDegraded || dismissed) return null; + + return ( + <div className="bg-accent border-b h-9 flex items-center justify-center gap-2 text-xs text-muted-foreground"> + <span> + For the best experience, open OpenCut in Chrome. + </span> + <Button + variant="text" + size="icon" + className="p-0 w-auto [&_svg]:size-3.5" + onClick={() => setDismissed(true)} + aria-label="Dismiss" + > + <HugeiconsIcon icon={Cancel01Icon} /> + </Button> + </div> + ); +} + +function EditorLayout() { + usePasteMedia(); + const { panels, setPanel } = usePanelStore(); + + return ( + <ResizablePanelGroup + direction="vertical" + className="size-full gap-[0.18rem]" + onLayout={(sizes) => { + setPanel("mainContent", sizes[0] ?? panels.mainContent); + setPanel("timeline", sizes[1] ?? panels.timeline); + }} + > + <ResizablePanel + defaultSize={panels.mainContent} + minSize={30} + maxSize={85} + className="min-h-0" + > + <ResizablePanelGroup + direction="horizontal" + className="size-full gap-[0.19rem] px-3" + onLayout={(sizes) => { + setPanel("tools", sizes[0] ?? panels.tools); + setPanel("preview", sizes[1] ?? panels.preview); + setPanel("properties", sizes[2] ?? panels.properties); + }} + > + <ResizablePanel + defaultSize={panels.tools} + minSize={15} + maxSize={40} + className="min-w-0" + > + <AssetsPanel /> + </ResizablePanel> + + <ResizableHandle withHandle /> + + <ResizablePanel + defaultSize={panels.preview} + minSize={30} + className="min-h-0 min-w-0 flex-1" + > + <PreviewPanel /> + </ResizablePanel> + + <ResizableHandle withHandle /> + + <ResizablePanel + defaultSize={panels.properties} + minSize={15} + maxSize={40} + className="min-w-0" + > + <PropertiesPanel /> + </ResizablePanel> + </ResizablePanelGroup> + </ResizablePanel> + + <ResizableHandle withHandle /> + + <ResizablePanel + defaultSize={panels.timeline} + minSize={15} + maxSize={70} + className="min-h-0 px-3 pb-3" + > + <Timeline /> + </ResizablePanel> + </ResizablePanelGroup> + ); +} |
